home *** CD-ROM | disk | FTP | other *** search
/ PC World Komputer 2010 April / PCWorld0410.iso / pluginy Firefox / 2410 / 2410.xpi / chrome / content / foxmarks-nodes.js < prev    next >
Text File  |  2010-01-28  |  26KB  |  796 lines

  1. /*
  2.  Copyright 2007-2008 Foxmarks Inc.
  3.  
  4.  foxmarks-nodes.js: implements class Node and Nodeset, encapsulating
  5.  our datamodel for the bookmarks.
  6.  
  7.  */
  8.  
  9. // To do:
  10. // * Add integrity checking to commands
  11. // * We're currently filtering out modified- and visited-only updates
  12. //   in the Compare algorithm. This is okay for modified but probably
  13. //   inappropriate for visited: visited will only get updated when some
  14. //   other attribute of the node changes, which may never happen. On the
  15. //   other hand, we don't want to sync every change to last visited. We
  16. //   may want to do something like a standard sync and a thorough sync;
  17. //   do a thorough sync either once a week or randomly 1 in 10 times. The
  18. //   thorough sync is when we'd perpetuate the last-visit updates.
  19. // * In merge algorithm, special treatment for toolbar folders.
  20.  
  21. // Module-wide constants
  22.  
  23. const NODE_ROOT     = "ROOT";
  24. const NODE_TOOLBAR  = "TOOLBAR";
  25. const PERMS_FILE    = 0644;
  26. const MODE_RDONLY   = 0x01;
  27. const MODE_WRONLY   = 0x02;
  28. const MODE_CREATE   = 0x08;
  29. const MODE_APPEND   = 0x10;
  30. const MODE_TRUNCATE = 0x20;
  31.  
  32. // class Node
  33.  
  34. function Node(nid, attrs) {
  35.     this.nid = nid;
  36.     for (a in attrs) {
  37.         if (attrs.hasOwnProperty(a)) this[a] = attrs[a];
  38.     }
  39.     // TODO: remove all the assumptions that ntype will be a bookmark
  40.     if (!this.ntype)
  41.         this.ntype = "bookmark";
  42.     //if (!this.ntype)
  43.     //    throw("Node.ntype now required");
  44. }
  45.  
  46. Node.prototype = {
  47.     constructor: Node,
  48.  
  49.     toSource: function() {
  50.         if (!this.ntype)
  51.             throw("Node.ntype now required");
  52.         return 'new Node("' + this.nid + '",' + 
  53.             this.GetSafeAttrs(true).toSource() + ')';
  54.     },
  55.  
  56.     GetSafeAttrs: function(withChildren) {
  57.         var attrs = {};
  58.  
  59.         for (var attr in this) {
  60.             if (this.hasOwnProperty(attr) && attr != 'nid' &&
  61.                 attr != 'private') {
  62.                 if (withChildren || attr != 'children') {
  63.                     attrs[attr] = this[attr];
  64.                 }
  65.             }
  66.         }
  67.  
  68.         return attrs;
  69.     },
  70.  
  71.     FindChild: function(nid) {
  72.         if (this["children"]) {
  73.             return this.children.indexOf(nid);
  74.         } else {
  75.             return -1;
  76.         }
  77.     }
  78. }
  79.  
  80. // class Nodeset
  81.  
  82. function Nodeset(datasource, cloneSource) {
  83.     if(datasource === undefined || datasource instanceof Nodeset)
  84.         throw("Nodeset() -- datasource is required");
  85.  
  86.     this.hash = null;
  87.     this._datasource = datasource;
  88.     this._cloneSource = cloneSource;
  89.     this._node = {};
  90.     this._callback = null;
  91.     this._length = cloneSource ? cloneSource.length : 0;
  92. }
  93.  
  94.  
  95.  
  96. Nodeset.FetchAdd = function(node) {
  97.     this.AddNode(node);
  98. }
  99.  
  100. Nodeset.FetchComplete = function(status) {
  101.     this._children = null;
  102.     this.callback(this.corrupt ? 1006 : status);
  103. }
  104.  
  105. var ct = {}
  106. Nodeset.Continue = {
  107.     notify: function(timer) {
  108.         var set = ct.self;
  109.         var nids = ct.nids;
  110.         var result;
  111.         var s = Date.now();
  112.         while (nids.length > 0 && Date.now() - s < 100) {
  113.             var next = nids.shift();
  114.             var nid = next[0];
  115.             var pnid = next[1];
  116.  
  117.             if (!set.Node(nid, false, true)) {
  118.                 Xmarks.LogWrite("Warning: OnTree() was about to reference " +
  119.                     nid + " which doesn't exist");
  120.                 break;
  121.             }
  122.  
  123.             try {
  124.                 result = ct.action.apply(ct.Caller, [nid, pnid]);
  125.             } catch (e) {
  126.                 if(typeof e == "number"){
  127.                     result = e;
  128.                 } else {
  129.                     Xmarks.LogWrite("OnTree error " + e.toSource());
  130.                     result = 3;
  131.                 }
  132.             }
  133.  
  134.             if (result)
  135.                 break;
  136.  
  137.             // if action above deleted nid...
  138.             if (set.Node(nid, false, true) == null)
  139.                 continue;
  140.  
  141.             if (set.Node(nid).ntype == "folder") {
  142.                 var children = set.Node(nid).children;
  143.                 var ix = 0;
  144.                 for (var child in children) {
  145.                     if (!children.hasOwnProperty(child))
  146.                         continue;
  147.                     if (ct.depthfirst) {
  148.                         nids.splice(ix++, 0, [children[child], nid]);
  149.                     } else {
  150.                         nids.push([children[child], nid]);
  151.                     }
  152.                 }
  153.             }
  154.         }
  155.  
  156.         if (nids.length > 0 && !result) {
  157.             timer.initWithCallback(Nodeset.Continue, 10,
  158.                 Ci.nsITimer.TYPE_ONE_SHOT);
  159.         } else {
  160.             ct.complete.apply(ct.Caller, [result]);
  161.         }
  162.     }
  163. }
  164.  
  165. Nodeset.prototype = {
  166.     constructor: Nodeset,
  167.  
  168.     get length() {
  169.         return this._length;
  170.     },
  171.  
  172.     NodeName: function(nid) {
  173.         var node = this.Node(nid, false, true);
  174.  
  175.         if (node && node.name) {
  176.             return node.name + "(" + nid + ")";
  177.         } else {
  178.             return nid;
  179.         }
  180.     },
  181.     handleNidConflict: function(lnode, snode, conflicts){
  182.         return this._datasource.handleNidConflict(lnode, snode, conflicts);
  183.     },
  184.         
  185.     AddNode: function(node) {
  186.         if (this._children && node.children) {
  187.             var self = this;
  188.             for (var index = 0; index < node.children.length; index++) {
  189.                 var cnid = node.children[index];
  190.                 if (self._children[cnid] != undefined) {
  191.                     node.children.splice(index--, 1);
  192.                     Xmarks.LogWrite("Warning: Filtering " + self.NodeName(cnid) + 
  193.                             " as a corrupted duplicate in parent " +
  194.                             node["name"] + " (" + node.nid + ")");
  195.                 } else {
  196.                     self._children[cnid] = true;
  197.                 }
  198.             }
  199.         }   
  200.             
  201.         if (this._node[node.nid]) { // Oh oh! Node already exists.
  202.             Xmarks.LogWrite("Warning: Node " + this.NodeName(node.nid) +
  203.                     " in folder " + this.NodeName(node.pnid) + 
  204.                     " already exists in folder " +
  205.                     this.NodeName(this._node[node.nid].pnid));
  206.             // Log error only; don't prevent sync as cleanup happened above
  207.             // this.corrupt = true;
  208.             return;
  209.         }
  210.         this._node[node.nid] = node;
  211.         this._length++;
  212.     },
  213.  
  214.     FetchFromNative: function(callback) {
  215.         this._children = {}
  216.         // this.source = new NativeDatasource();
  217.         this.callback = callback;
  218.         this._datasource.ProvideNodes(this, Nodeset.FetchAdd, Nodeset.FetchComplete);
  219.     },
  220.  
  221.     BaselineLoaded: function(baseline, callback) {
  222.         return this._datasource.BaselineLoaded(baseline, callback);
  223.     },
  224.     FlushToNative: function(callback) {
  225.         // var source = new NativeDatasource();
  226.         this._datasource.AcceptNodes(this, callback);
  227.         return;
  228.     },
  229.  
  230.     ProvideCommandset: function(callback) {
  231.         var self = this;
  232.         var cs = new Commandset();
  233.  
  234.         this.OnTree(Add, Done);
  235.         return;
  236.             
  237.         function Add(nid, pnid) {
  238.             cs.append(new Command("insert", nid,
  239.                 self.Node(nid).GetSafeAttrs()));
  240.             return 0;
  241.         }
  242.  
  243.         function Done(status) {
  244.             callback(status, cs);
  245.         }
  246.     },
  247.  
  248.     _GetFile: function() {
  249.         var file = Cc['@mozilla.org/file/directory_service;1']
  250.             .getService(Ci.nsIProperties)
  251.             .get('ProfD', Ci.nsIFile);
  252.  
  253.         file.append(this._datasource.getBaselineName());
  254.         return file;
  255.     },
  256.  
  257.     SaveToFile: function(callback) {
  258.  
  259.         var self = this;
  260.         var first = true;
  261.  
  262.         var file = this._GetFile();
  263.  
  264.         var fstream = Cc["@mozilla.org/network/safe-file-output-stream;1"]
  265.             .createInstance(Ci.nsIFileOutputStream);
  266.         fstream.init(file, (MODE_WRONLY | MODE_TRUNCATE | MODE_CREATE), 
  267.             PERMS_FILE, 0);
  268.  
  269.         var cstream = Cc["@mozilla.org/intl/converter-output-stream;1"]
  270.             .createInstance(Ci.nsIConverterOutputStream);
  271.         cstream.init(fstream, "UTF-8", 0, 0x0000);
  272.  
  273.         cstream.writeString('({ version:"'+ Xmarks.FoxmarksVersion() +
  274.                 '", currentRevision:' + self.currentRevision + 
  275.                 ', _node: {' );
  276.  
  277.         this.OnTree(WriteNode, WriteDone);
  278.         return;
  279.  
  280.         function WriteNode(nid, pnid) {
  281.             var node = self.Node(nid);
  282.             cstream.writeString((first ? "" : ",") + 
  283.                     "'" + nid.replace(/'/g, "\\'") + "'" + ":" + 
  284.                     node.toSource());
  285.             first = false;
  286.             return 0;
  287.         }
  288.  
  289.         function WriteDone(status) {
  290.             if (!status) {
  291.                 cstream.writeString("}})");
  292.                 // Flush the character converter, then finish the file stream,
  293.                 // guaranteeing that an existing file isn't overwritten unless
  294.                 // the whole thing succeeds.
  295.                 cstream.flush();            
  296.                 try {
  297.                     fstream.QueryInterface(Ci.nsISafeOutputStream).finish();
  298.                 } catch (e) {
  299.                     fstream.close();
  300.                     Xmarks.LogWrite("Error in Writing: " + e.message);
  301.                     status = 1009;
  302.                 }
  303.             }
  304.             callback(status);
  305.         }
  306.  
  307.     },
  308.     
  309.  
  310.     LoadFromFile: function() {
  311.         var file = this._GetFile();
  312.         var fstream = Cc["@mozilla.org/network/file-input-stream;1"]
  313.             .createInstance(Ci.nsIFileInputStream);
  314.         fstream.init(file, MODE_RDONLY, PERMS_FILE, 0);
  315.         var cstream = Cc["@mozilla.org/intl/converter-input-stream;1"]
  316.             .createInstance(Ci.nsIConverterInputStream);
  317.         cstream.init(fstream, "UTF-8", 32768, 0xFFFD);
  318.         var str = {}; 
  319.         
  320.         var contents = "";
  321.  
  322.         while (cstream.readString(32768, str) != 0) {
  323.             contents += str.value;
  324.         }
  325.         fstream.close();
  326.         cstream.close();
  327.  
  328.         var result = eval(contents);
  329.  
  330.         if (result["version"]) {
  331.             this._node = result._node;
  332.             this.currentRevision = result.currentRevision;
  333.             this.version = result.version;
  334.         } else {    // For backwards compatibility
  335.             this._node = result;
  336.             Xmarks.LogWrite("Baseline file has no currentRevision");
  337.             this.currentRevision = Xmarks.gSettings.GetSyncRevision(this._datasource.syncType);
  338.         }
  339.  
  340.         var self = this;
  341.         self._length = 0;
  342.         forEach(this._node, function() { self._length++; } );
  343.     },
  344.  
  345.  
  346.     Declone: function(callback) {
  347.         // If we are cloned from some other nodeset, copy any references
  348.         // we currently hold from the clonesource into ourselves and
  349.         // break the clonesource relationship.
  350.         // This must be done before serializing a nodeset to disk.
  351.  
  352.         var self = this;
  353.  
  354.         if (!this._cloneSource) {
  355.             callback(0);
  356.         } else {
  357.             this.OnTree(CopyNode, Done);
  358.         }
  359.         return;
  360.  
  361.         function CopyNode(nid, pnid) {
  362.             if (self._node[nid] === undefined) {
  363.                 self._node[nid] = self._cloneSource.Node(nid);
  364.             }
  365.             return 0;
  366.         }
  367.  
  368.         function Done(status) {
  369.             if (!status) {
  370.                 self._cloneSource = null;
  371.                 forEach(self._node, 
  372.                     function(v, k) { 
  373.                         if (!v) {
  374.                             delete self._node[k];
  375.                             self._length--;
  376.                         }
  377.                     }
  378.                 ); 
  379.             }
  380.             callback(status);
  381.         }
  382.     },
  383.  
  384.     // Node returns the node with the given nid.
  385.     // If you intend to modify the returned node,
  386.     // set "write" true; this will do a "copy on write"
  387.     // from the clone source if one has been set.
  388.     // If node specified is not found, throws an exception
  389.     // unless "nullOkay" is true, in which case it returns null.
  390.  
  391.     Node: function(nid, write, nullOkay) {
  392.         if (nid in this._node) {
  393.             return this._node[nid];
  394.         } else if (this._cloneSource) {
  395.             var node = this._cloneSource.Node(nid, false, nullOkay);
  396.             if (!node || !write) {
  397.                 return node;
  398.             } else {
  399.                 var newNode = node.clone(true);
  400.                 if(newNode.private)
  401.                     delete newNode.private;
  402.                 this.AddNode(newNode);
  403.                 return newNode;
  404.             }
  405.         } else {
  406.             if (nullOkay)
  407.                 return null;
  408.             else
  409.                 throw Error("Node not found: " + nid);
  410.         }
  411.     },
  412.  
  413.     HasAncestor: function(nid, pnid) {
  414.         while (nid) {
  415.             var node = this.Node(nid, false, true);
  416.             if (!node) {
  417.                 Xmarks.LogWrite("Whoops! HasAncestor tried to reference " + nid +
  418.                         " which doesn't exist");
  419.                 throw Error("HasAncestor bad nid " + nid);
  420.             }
  421.             nid = node.pnid;
  422.             if (nid == pnid)
  423.                 return true;
  424.         }
  425.         return false;
  426.     },
  427.  
  428.     // Find next sibling in this folder that also exists in other's folder
  429.     NextSibling: function(nid, other) {
  430.         var pnid = this.Node(nid).pnid;
  431.         var oursibs = this.Node(pnid).children;
  432.         var othersibs = other.Node(pnid).children;
  433.  
  434.         for (var i = oursibs.indexOf(nid) + 1; i < oursibs.length; ++i) {
  435.             var sib = oursibs[i];
  436.             if (othersibs.indexOf(sib) >= 0) {
  437.                 return sib;
  438.             }
  439.         }
  440.  
  441.         return null;
  442.     },
  443.  
  444.     InsertInParent: function(nid, pnid, bnid) {
  445.         if (nid == NODE_ROOT && pnid == null) {
  446.             return; // Fail silently.
  447.         }
  448.  
  449.         if (!nid)
  450.             throw Error("bad nid");
  451.  
  452.         if (!pnid)
  453.             throw Error("bad pnid for nid " + nid);
  454.  
  455.         var parent = this.Node(pnid, true);
  456.         if (typeof parent["children"] == "undefined") {
  457.             parent.children = [];
  458.         }
  459.         if (parent.children.indexOf(nid) >= 0) {
  460.             throw Error("child " + nid + " already exists in parent " + pnid);
  461.         }
  462.         if (bnid) {
  463.             var i = parent.children.indexOf(bnid);
  464.             if (i >= 0) {
  465.                 parent.children.splice(i, 0, nid);
  466.             } else {
  467.                 throw Error("didn't find child " + bnid + " in parent " + pnid);
  468.             }
  469.         } else {
  470.             parent.children.push(nid);
  471.         }
  472.         this.Node(nid, true).pnid = pnid;
  473.     },            
  474.  
  475.     RemoveFromParent: function(nid) {
  476.         var node = this.Node(nid, true);
  477.         if (!node.pnid) {
  478.             Xmarks.LogWrite("node.pnid is undefined for " + node.name);
  479.         }
  480.         var parent = this.Node(node.pnid, true);
  481.         var i = parent.FindChild(nid);
  482.  
  483.         if (i >= 0) {
  484.             parent.children.splice(i, 1);
  485.         } else {
  486.             throw Error("didn't find child " + nid + " in parent " + pnid);
  487.         }
  488.         node.pnid = null;
  489.     },
  490.  
  491.     Do_insert: function(nid, args /* pnid, bnid, ntype, etc. */) {
  492. //        Xmarks.LogWrite("inserting " + nid + " " + args.toSource());
  493.         if (this.Node(nid, false, true) != null) {
  494.             var nargs = this.Node(nid).GetSafeAttrs();
  495.             var conflict = false;
  496.             forEach(args, function(value, attr) {
  497.                 if (nargs[attr] && value != nargs[attr]) {
  498.                     conflict = true;
  499.                 }
  500.             } );
  501.             if (conflict) {
  502.                 throw Error("Tried to insert a node that already exists");
  503.             } else {
  504.                 return; // In the interests of being accomodating, we're going
  505.                         // to let this one slide by. But make sure it doesn't
  506.                         // happen again, mkay?
  507.             }
  508.         }
  509.         var node = new Node(nid);
  510.  
  511.         for (attr in args) {
  512.             if (args.hasOwnProperty(attr) && 
  513.                     attr != 'pnid' && attr != 'bnid' && attr != 'children') {
  514.                 node[attr] = args[attr];
  515.             }
  516.         }
  517.  
  518.         this.AddNode(node);
  519.         this.InsertInParent(nid, args.pnid, args.bnid);
  520.     },
  521.  
  522.     Do_delete: function(nid) {
  523.         var self = this;
  524. //        Xmarks.LogWrite("deleting " + this.NodeName(nid));
  525.  
  526.         // Be careful here: only the top-level node has to be
  527.         // removed from its parent. That node and its descendants
  528.         // need to be nulled out.
  529.  
  530.         function NukeNode(nid) {
  531.             var node = self.Node(nid);
  532.             if (node.children) {
  533.                 for (var n = 0; n < node.children.length; ++n)
  534.                     NukeNode(node.children[n]);
  535.             }
  536.             if (self._cloneSource) {
  537.                 self._node[nid] = null; // If cloned, shadow deletion.
  538.             } else {
  539.                 delete self._node[nid]; // Otherwise, delete it outright.
  540.             }
  541.             self._length--;
  542.         }
  543.  
  544.         self.RemoveFromParent(nid);
  545.         NukeNode(nid);
  546.     },
  547.  
  548.     Do_move: function(nid, args /* pnid, bnid */) {
  549. //        Xmarks.LogWrite("moving " + this.NodeName(nid));
  550.  
  551.         this.RemoveFromParent(nid);
  552.         this.InsertInParent(nid, args.pnid, args.bnid);
  553.     },
  554.  
  555.     Do_reorder: function(nid, args /* bnid */) {
  556. //        Xmarks.LogWrite("reordering " + this.NodeName(nid) + " before " + 
  557. //                this.NodeName(args.bnid));
  558.         var pnid = this.Node(nid).pnid;
  559.         this.RemoveFromParent(nid);
  560.         this.InsertInParent(nid, pnid, args.bnid);
  561.     },
  562.  
  563.     Do_update: function(nid, args /* attrs */) {
  564. //        Xmarks.LogWrite("updating " + this.NodeName(nid));
  565.         var node = this.Node(nid, true);
  566.  
  567.         forEach(args, function(value, attr) {
  568.             if (value) {
  569.                 node[attr] = value;
  570.             } else {
  571.                 delete node[attr];
  572.             }
  573.         } );
  574.     },
  575.  
  576.     // Pass either a single command or a Commandset.
  577.     Execute: function(command) {
  578.         if (command instanceof Commandset) {
  579.             var self = this;
  580.             forEach(command.set, function(c) {
  581.                 self.Execute(c);
  582.             } );
  583.             return;
  584.         }
  585.  
  586.         var method = this["Do_" + command.action];
  587.         try {
  588.             method.apply(this, [command.nid, command.args]);
  589.         } catch (e) {
  590.             if(typeof e == "number") {
  591.                 throw e;
  592.             } else {
  593.                 Components.utils.reportError(e);
  594.                 throw Error("Failed executing command " + command.toSource() + 
  595.                         "; error is " + e.toSource());
  596.             }
  597.         }
  598.     },
  599.     OrderIsImportant: function(){
  600.         return this._datasource.orderIsImportant;
  601.     },
  602.  
  603.     // traverses this's bookmarks hierarchy starting with
  604.     // startnode, calling action(node) for each node in the tree,
  605.     // then calling complete() when traversal is done.
  606.     // enforces rules about maximum run times to prevent hanging the UI
  607.     // when traversing large trees or when running on slow CPU's.
  608.     // action() should return 0 to continue, non-zero status to abort.
  609.     // complete() is called with status, non-zero if aborted.
  610.     // depthfirst determines tree traversal order
  611.  
  612.     OnTree: function(action, complete, startnid, depthfirst) {
  613.         ct = {}
  614.         ct.self = this;
  615.         ct.Caller = this;
  616.         ct.action = action;
  617.         ct.complete = complete;
  618.         ct.startnid = startnid || NODE_ROOT;
  619.         ct.depthfirst = depthfirst;
  620.         ct.nids = [[ct.startnid, null]];
  621.         ct.timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
  622.         ct.timer.initWithCallback(Nodeset.Continue, 10, 
  623.             Ci.nsITimer.TYPE_ONE_SHOT);
  624.         return;
  625.     },
  626.  
  627.     IGNORABLE: { created: true, visited: true, modified: true },
  628.  
  629.     // Compare this nodeset with another, returning a canonical list
  630.     // of commands that transforms this nodeset into the specified one.
  631.     // Note that at the successful conclusion of this routine, this
  632.     // nodeset will be transformed to match the specified nodeset.
  633.     Compare: function(other, callback) {
  634.         var self = this;
  635.         var commandset = new Commandset();
  636.  
  637.         Step1();
  638.         return;
  639.  
  640.         function Step1() {
  641.             self.OnTree(FindReordersInsertsMoves, Step2);
  642.         }
  643.  
  644.         function Step2(status) {
  645.             if (status) {
  646.                 callback(status);
  647.             } else {
  648.                 self.OnTree(FindDeletes, Step4);
  649.             }
  650.         }
  651.  
  652.         // There IS no step 3.
  653.  
  654.         function Step4(status) {
  655.             if (status) {
  656.                 callback(status);
  657.             } else {
  658.                 // Make sure we compare root nodes for changes.
  659.                 try {
  660.                     var sroot = self.Node(NODE_ROOT);
  661.                     var oroot = other.Node(NODE_ROOT);
  662.                     var attrs = {};
  663.                     if (self._datasource.compareNodes(sroot, oroot, attrs)) {
  664.                         var command = new Command("update", NODE_ROOT, attrs);
  665.                         commandset.append(command);
  666.                         self.Execute(command);
  667.                     }
  668.                 } catch (e) {
  669.                    Xmarks.LogWrite("Error comparing roots: " + e.toSource());
  670.                 }
  671.                 self.OnTree(FindUpdates, Step5);
  672.             }
  673.         }
  674.  
  675.         function Step5(status) {
  676.             if (status) {
  677.                 callback(status);
  678.             } else {
  679.                 callback(0, commandset);
  680.             }
  681.         }
  682.  
  683.         function FindReordersInsertsMoves(nid, pid) {
  684.             if (self.Node(nid).ntype != "folder")
  685.                 return 0;
  686.  
  687.             var snode = self.Node(nid);
  688.             var onode = other.Node(nid, false, true);
  689.             if (!onode) // Deleted; don't worry about children.
  690.                 return 0;
  691.  
  692.             var us = snode.children ? snode.children.slice() : [];
  693.             var them = onode.children ? onode.children.slice() : [];
  694.  
  695.             // Reduce us and them to intersections
  696.             us = us.filter(function(x) { return them.indexOf(x) >= 0; } );
  697.             them = them.filter(function(x) { return us.indexOf(x) >= 0; } );
  698.  
  699.             if (us.length != them.length) {
  700.                 Xmarks.LogWrite("Error: intersections of unequal length for " +
  701.                         self.NodeName(nid));
  702.                 Xmarks.LogWrite("us   = " + us);
  703.                 Xmarks.LogWrite("them = " + them);
  704.                 throw Error("Intersections of unequal length");
  705.             }
  706.  
  707.             // Reorder us according to them
  708.             if(self._datasource.orderIsImportant){
  709.                 for (var i = 0; i < us.length; ++i) {
  710.                     if (us[i] != them[i]) {
  711.                         var command = new Command("reorder", them[i], 
  712.                             { bnid: us[i] });
  713.                         commandset.append(command);
  714.                         self.Execute(command);
  715.                         // Simulate reorder in our intersected list
  716.                         us.splice(us.indexOf(them[i]), 1);
  717.                         us.splice(i, 0, them[i]);
  718.                     }
  719.                 }
  720.             }
  721.  
  722.  
  723.             // Walk through them to find inserts and moves
  724.             var sc = self.Node(nid).children || [];     // (May have changed)
  725.             var oc = onode.children || [];
  726.             forEach(oc, function(child, index) {
  727.                 if (sc.indexOf(child) < 0) {    // ... missing from us
  728.                     if (self.Node(child, false, true)) {    // ... but exists in set
  729.                         var command = new Command("move", child, 
  730.                             { pnid: nid, bnid: FindBnid(index + 1) } );
  731.                         commandset.append(command);
  732.                         self.Execute(command);
  733.                     } else {                                // ... missing entirely
  734.                         var attrs = other.Node(child).GetSafeAttrs();
  735.                         attrs.bnid = FindBnid(index + 1);
  736.                         var command = new Command("insert", child, attrs);
  737.                         commandset.append(command);
  738.                         self.Execute(command);
  739.                     }
  740.                 }
  741.  
  742.                 function FindBnid(index) {
  743.                     var oc = onode.children;
  744.                     var len = oc.length;
  745.                     while (index < len && us.indexOf(oc[index]) < 0) {
  746.                         ++index;
  747.                     }
  748.                     return index < len ? oc[index] : null;
  749.                 }
  750.             } );
  751.  
  752.             return 0;
  753.         }
  754.  
  755.         function FindDeletes(nid, pnid) {
  756.             if (!other.Node(nid, false, true)) {
  757.                 var command = new Command("delete", nid);
  758.                 commandset.append(command);
  759.                 self.Execute(command);
  760.             }
  761.             return 0;
  762.         }
  763.  
  764.         function FindUpdates(nid, pnid) {
  765.             var result = 0;
  766.             try {
  767.                 var snode = self.Node(nid);
  768.                 var onode = other.Node(nid);
  769.                 var attrs = {};
  770.                 if (self._datasource.compareNodes(snode, onode, attrs)) {
  771.                     var command = new Command("update", nid, attrs);
  772.                     commandset.append(command);
  773.                     self.Execute(command);
  774.                 }
  775.             } catch (e){
  776.                 if(typeof e != "number"){
  777.                     Components.utils.reportError(e);
  778.                     result = 4;
  779.                 } else {
  780.                     result = e;
  781.                 }
  782.             }
  783.             return result;
  784.         }
  785.  
  786.  
  787.     },
  788.  
  789.     Merge: function(source){
  790.         this._datasource.merge(this, source);
  791.     },
  792.  
  793.  
  794. };
  795.  
  796.